package org.example.DesignPatterns.Proxy.AOP;

import org.example.annotation.DIContainer.SimpleInject;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

import static org.springframework.util.ClassUtils.getMethod;


/**
 * @description:
 * @author: farprt
 * @create: 2022-08-14 16:28
 **/
public class CGLibContainer {
    //表示每个类的每个切点的方法列表
    static Map<Class, Map<InterceptPoint, List<Method>>> interceptMethodsMap=new HashMap<>();
    //分析每个带有@Aspect注解的类，这些类一般可以通过扫描所有的类得到，为简化起见，我们将它们写在代码中
    static Class[] aspects=new Class[]{ServiceLogAspect.class,ExceptionAspect.class};

    static {
        init();
    }
    private static void init() {
        //对每个切面，即带有@Aspect注解的类cls，查找其before/after/exception方法，调用方法addInterceptMethod将其加入目标类的切点方法列表中
        for(Class<? > cls : aspects) {
            Aspect aspect = cls.getAnnotation(Aspect.class);
            if(aspect!= null) {
                Method before = getMethod(cls, "before", new Class[] {Object.class, Method.class, Object[].class });
                Method after = getMethod(cls, "after", new Class[] {Object.class, Method.class, Object[].class, Object.class });
                Method exception = getMethod(cls, "exception", new Class[] {Object.class, Method.class, Object[].class, Throwable.class });
                Class<? >[] intercepttedArr = aspect.value();
                for(Class<? > interceptted : intercepttedArr) {
                    addInterceptMethod(interceptted, InterceptPoint.BEFORE, before);
                    addInterceptMethod(interceptted, InterceptPoint.AFTER, after);
                    addInterceptMethod(interceptted, InterceptPoint.EXCEPTION, exception);
                }
            }
        }
    }

    private static void addInterceptMethod(Class cls,InterceptPoint point,Method method){
        if(method==null){
            return;
        }
        Map<InterceptPoint,List<Method>> map=interceptMethodsMap.get(cls);
        if(map==null){
            map=new HashMap<>();
            interceptMethodsMap.put(cls,map);
        }
        List<Method> methods=map.get(point);
        if(methods==null){
            methods=new ArrayList<>();
            map.put(point,methods);
        }
        methods.add(method);
    }

    private static <T> T createInstance(Class cls) throws IllegalAccessException, InstantiationException {
        if(!interceptMethodsMap.containsKey(cls)){
            return (T) cls.newInstance();
        }
        Enhancer enhancer=new Enhancer();
        enhancer.setSuperclass(cls);
        enhancer.setCallback(new AspectInterceptor());
        return (T) enhancer.create();
    }

    /*
    它根据原始类的实际类型查找应该执行的before/after/exception方法列表，
    在调用原始方法前执行before方法，执行后执行after方法，出现异常时执行exception方法
     */

    static class AspectInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            //执行before方法
            List<Method> beforeMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.BEFORE);
            for(Method m : beforeMethods) {
                m.invoke(null, new Object[] { object, method, args });
            }
            try {
                //调用原始方法
                Object result = proxy.invokeSuper(object, args);
                //执行after方法
                List<Method> afterMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.AFTER);
                for(Method m : afterMethods) {
                    m.invoke(null, new Object[] { object, method, args, result });
                }
                return result;
            } catch (Throwable e) {
                //执行exception方法
                List<Method> exceptionMethods = getInterceptMethods(object.getClass().getSuperclass(), InterceptPoint.EXCEPTION);
                for(Method m : exceptionMethods) {
                    m.invoke(null, new Object[] { object, method, args, e });
                }
                throw e;
            }
        }
    }

    static List<Method> getInterceptMethods(Class cls,InterceptPoint point){
        Map<InterceptPoint,List<Method>> map=interceptMethodsMap.get(cls);
        if(map==null){
            return Collections.emptyList();
        }
        List<Method> methods=map.get(point);
        if(methods==null){
            return Collections.emptyList();
        }
        return methods;
    }

    public static <T> T getInstance(Class cls){
        try {
            T obj=createInstance(cls);
            Field[] fields=cls.getDeclaredFields();
            for(Field field:fields){
                if(field.isAnnotationPresent(SimpleInject.class)){
                    if(!field.isAccessible()){
                        field.setAccessible(true);
                    }
                    Class fileIdCls=field.getType();
                    field.set(obj,getInstance(fileIdCls));
                }
            }
            return obj;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
}